#include <iostream>
#include <functional>
#include <memory>
#include "request.pb.h"
#include "muduo/proto/codec.h"
#include "muduo/proto/dispatcher_lite.h"
#include "muduo/proto/dispatcher.h"
#include "muduo/net/EventLoopThread.h"
#include "muduo/net/TcpClient.h"
#include "muduo/net/TcpConnection.h"
#include "muduo/base/CountDownLatch.h"
#include "muduo/base/Mutex.h"
using namespace std;

class ProtoClient
{
public:
	typedef shared_ptr<google::protobuf::Message> MessagePtr;
    typedef shared_ptr<request::TranslateRequest> TranslateRequestPtr;
    typedef shared_ptr<request::TranslateResponse> TranslateResponsePtr;
    typedef shared_ptr<request::CalculatorRequest> CalculatorRequestPtr;
    typedef shared_ptr<request::CalculatorResponse> CalculatorResponsePtr;
	ProtoClient(const std::string& ip, int port)
		: _latch(1)
		, _client(_loopthread.startLoop(), muduo::net::InetAddress(ip, port), "Client")
		, _dispatcher(bind(&ProtoClient::onUnknownMessage, this, placeholders::_1, placeholders::_2, placeholders::_3))
		, _codec(bind(&ProtobufDispatcher::onProtobufMessage, &_dispatcher, placeholders::_1, placeholders::_2, placeholders::_3))
	{
		//注册业务回调函数
		_dispatcher.registerMessageCallback<request::TranslateResponse>(bind(&ProtoClient::translateResp, this, placeholders::_1, placeholders::_2, placeholders::_3));
		_dispatcher.registerMessageCallback<request::CalculatorResponse>(bind(&ProtoClient::calculatorResp, this, placeholders::_1, placeholders::_2, placeholders::_3));

		//注册连接以及消息回调
		_client.setConnectionCallback(std::bind(&ProtoClient::onConnection, this, placeholders::_1));
		_client.setMessageCallback(bind(&ProtobufCodec::onMessage, &_codec, placeholders::_1, placeholders::_2, placeholders::_3));
	}

	void connect()
	{
		_client.connect();
		_latch.wait();
	}

	void translate()
	{
		std::cout << "请输入你要翻译的单词：";
		string word;
		getline(cin, word);
		request::TranslateRequest req;
		req.set_word(word);
		send(req);
	}

	void calculator()
	{
		std::cout << "请输入操作数1：";
		int num1 = 0;
		cin >> num1;
		getchar();
		std::cout << "请输入操作符：";
		string op;
		getline(cin, op);
		std::cout << "请输入操作数2：";
		int num2 = 0;
		cin >> num2;
		getchar();
		request::CalculatorRequest req;
		req.set_num1(num1);
		req.set_op(op);
		req.set_num2(num2);
		send(req);
	}

private:
	void translateResp(const muduo::net::TcpConnectionPtr& conn, const TranslateResponsePtr& req, muduo::Timestamp rt)
	{
		cout << "结果为：" << req->result() << endl;
	}

	void calculatorResp(const muduo::net::TcpConnectionPtr& conn, const CalculatorResponsePtr& req, muduo::Timestamp rt)
	{
		if (req->code() == false)
		{
			cout << "发生错误" << endl;
		}
		else 
		{
			cout << "结果为：" << req->result() << endl;
		}
	}

	//多态
	void send(const google::protobuf::Message& msg)
	{
		if (_conn->connected())
			_codec.send(_conn, msg);
	}

    void onConnection(const muduo::net::TcpConnectionPtr& conn)
    {
        if (conn->connected())
        {
            cout << "连接成功！！！" << endl;
			_latch.countDown();
			_conn = conn;
        }
        else 
        {
            cout << "断开连接" << endl;
			_conn.reset();
        }
    }

    void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn, const MessagePtr& message, muduo::Timestamp)
    {
        std::cout << "onUnknownMessage: " << message->GetTypeName() << std::endl;
		conn->shutdown();
    }

private:
	muduo::CountDownLatch _latch; //实现同步
	muduo::net::EventLoopThread _loopthread; //异步循环处理线程
	muduo::net::TcpConnectionPtr _conn; //客户端与服务器的连接
	muduo::net::TcpClient _client;    //客户端
	ProtobufDispatcher _dispatcher; //请求分发器
	ProtobufCodec _codec; //协议处理器
};

void menu()
{
	cout << "**********************" << endl;
	cout << "*     1.translate    *" << endl;
	cout << "*     2.calculator   *" << endl;
	cout << "*     0.exit         *" << endl;
	cout << "**********************" << endl;
}

int main()
{
	ProtoClient client("127.0.0.1", 8080);
	client.connect();

	int choose = 1;
	while (choose)
	{
		menu();
		cout << "请选择：";
		cin >> choose;
		getchar();
		switch (choose)
		{
		case 1:
			client.translate();
			break;
		case 2:
			client.calculator();
			break;
		case 0:
			break;
		default:
			cout << "选择失败，请重新选择" << endl;
			break;
		}
	}

	return 0;
}
